﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using AMM.Model;
using AMM.Model.Store;

namespace AMM.DataAccess.Store {
    public class ProcurementOrderRepository : BaseRepository<ProcurementOrder> {

        private int _pageSize;

        public ProcurementOrderRepository(int pageSize = int.MaxValue) {
            this._pageSize = pageSize;
        }

        public ProcurementOrder FetchProcurementOrderById(int procurementOrderId) {
            using (var ctx = this.Context) {
                return ctx.ProcurementOrders
                    .Include("Vendor")
                    .Include("ProcurementOrderDetails")
                    .FirstOrDefault(x => x.ProcurementOrderId == procurementOrderId);
            }
        }

        public ProcurementOrder FetchProcurementOrderByIdForView(int procurementOrderId) {
            using (var ctx = this.Context) {
                return ctx.ProcurementOrders
                    .Include("ProcurementOrderDetails")
                    .Include("ProcurementOrderDetails.Material")
                    .Include("ProcurementOrderDetails.Material.Basic1")
                    .Include("ProcurementOrderDetails.Basic")
                    .FirstOrDefault(x => x.ProcurementOrderId == procurementOrderId);
            }
        }

        public List<ProcurementOrderDetail> FetchMaterialViewModelListByProcumentOrderId(int procurementOrderId) {
            using (var ctx = this.Context) {
                return ctx.ProcurementOrderDetails
                    .Include("Material")
                    .Include("Material.Basic")
                    .Where(x => x.ProcurementOrderId == procurementOrderId).ToList();
            }
        }

        public void UpdateProcurementOrder(ProcurementOrder procurementOrder) {
            using (var ctx = this.Context) {
                using (DbContextTransaction trans = ctx.Database.BeginTransaction()) {
                    try {
                        procurementOrder.ProcurementOrderDetails.Where(x => x.ClassStatus == ClassStatus.Added).ToList().ForEach(x => {
                            x.ProcurementOrder = procurementOrder;
                            x.ProcurementOrderId = procurementOrder.ProcurementOrderId;
                            x.ProcurementOrderDetailsId = Guid.NewGuid().GetHashCode();
                        });
                        // do not try to merge with above codes, because you can't change the fields which was marked as primary key
                        procurementOrder.ProcurementOrderDetails.ToList().ForEach(x => {
                            ctx.ProcurementOrderDetails.Attach(x);
                            switch (x.ClassStatus) {
                                case ClassStatus.UnChanged:
                                    ctx.ProcurementOrderDetails.Attach(x);
                                    ctx.Entry(x).State = EntityState.Unchanged;
                                    break;
                                case ClassStatus.Updated:
                                    x.ModifiedBy = procurementOrder.ModifiedBy;
                                    x.ModifiedDatetime = DateTime.Now;
                                    ctx.ProcurementOrderDetails.Attach(x);
                                    ctx.Entry(x).State = EntityState.Modified;
                                    break;
                                case ClassStatus.Added:
                                    x.CreatedBy = procurementOrder.ModifiedBy;
                                    x.CreatedDatetime = DateTime.Now;
                                    ctx.ProcurementOrderDetails.Attach(x);
                                    ctx.Entry(x).State = EntityState.Added;
                                    break;
                                case ClassStatus.Deleted:
                                    ctx.ProcurementOrderDetails.Attach(x);
                                    ctx.Entry(x).State = EntityState.Deleted;
                                    break;
                            }
                        });

                        ctx.ProcurementOrders.Attach(procurementOrder);
                        ctx.Entry(procurementOrder).State = EntityState.Modified;
                        ctx.SaveChanges();
                        trans.Commit();
                    }
                    catch (Exception ex) {
                        trans.Rollback();
                        throw ex;
                    }
                }
            }
        }

        public void DeleteMultipleProcurementOrder(params int[] procurementOrderIds) {
            using (var ctx = this.Context) {
                using (DbContextTransaction trans = ctx.Database.BeginTransaction()) {
                    try {
                        ctx.ProcurementOrderDetails.Where(x => procurementOrderIds.Contains(x.ProcurementOrderId)).ToList().ForEach(x => {
                            ctx.ProcurementOrderDetails.Remove(x);
                        });
                        ctx.ProcurementOrders.Where(x => procurementOrderIds.Contains(x.ProcurementOrderId)).ToList().ForEach(x => {
                            ctx.ProcurementOrders.Remove(x);
                        });
                        ctx.SaveChanges();
                        trans.Commit();
                    }
                    catch (Exception ex) {
                        trans.Rollback();
                        throw ex;
                    }
                }
            }
        }

        public List<ProcurementOrder> FetchProcurementOrderList(ProcurementOrderSearchModel searchModel, int pageIndex) {
            using (var ctx = this.Context) {
                var query = ctx.ProcurementOrders
                    .Include("Vendor")
                    .Include("Vendor.User")
                    .Include("ProcurementOrderDetails")
                    .Include("ProcurementOrderDetails.Material")
                    .Include("ProcurementOrderDetails.Basic")
                    .Where(x => !string.IsNullOrEmpty(x.Code));

                if (!string.IsNullOrEmpty(searchModel.Code)) {
                    query = query.Where(x => x.Code.Contains(searchModel.Code));
                }
                if (!string.IsNullOrEmpty(searchModel.MaterialCode)) {
                    query = query.Where(x => x.ProcurementOrderDetails.Any(y => y.Material.Code.Contains(searchModel.MaterialCode)));
                }
                if (!string.IsNullOrEmpty(searchModel.User)) {
                    query = query.Where(x => x.Vendor.User.Name.Contains(searchModel.User));
                }
                if (searchModel.CreateDateStart.HasValue) {
                    query = query.Where(x => x.CreatedDatetime >= DbFunctions.TruncateTime(searchModel.CreateDateStart.Value));
                }
                if (searchModel.CreateDateEnd.HasValue) {
                    query = query.Where(x => x.CreatedDatetime <= DbFunctions.TruncateTime(searchModel.CreateDateEnd.Value));
                }
                if (searchModel.DeliverDateStart.HasValue) {
                    query = query.Where(x => x.ProcurementOrderDetails.Any(y => y.DeliveryDate >= DbFunctions.TruncateTime(searchModel.DeliverDateStart.Value)));
                }
                if (searchModel.DeliverDateEnd.HasValue) {
                    query = query.Where(x => x.ProcurementOrderDetails.Any(y => y.DeliveryDate <= DbFunctions.TruncateTime(searchModel.DeliverDateEnd.Value)));
                }
                if (searchModel.PayCurrencyId.HasValue) {
                    query = query.Where(x => x.Vendor.PayCurrencyId == searchModel.PayCurrencyId.Value);
                }
                if (searchModel.VendorId.HasValue) {
                    query = query.Where(x => x.VendorId == searchModel.VendorId.Value);
                }
                if (searchModel.SourceId.HasValue) {
                    query = query.Where(x => x.ProcurementOrderDetails.Any(y => y.Basic.BasicsId == searchModel.SourceId.Value));
                }
                if (searchModel.DeliverStatus.HasValue) {
                    switch (searchModel.DeliverStatus.Value) {
                        case 1: // 已交齐
                            query = query.Where(x => x.ProcurementOrderDetails.Any(y => y.SuspenseQuantity == y.Quantity));
                            break;
                        case 2: // 已交货
                            query = query.Where(x => x.ProcurementOrderDetails.Any(y => y.SuspenseQuantity > 0));
                            break;
                        case 3: // 未交货
                            query = query.Where(x => x.ProcurementOrderDetails.Any(y => y.SuspenseQuantity == 0));
                            break;
                        case 4: // 已交货 && 未交货
                            query = query.Where(x => x.ProcurementOrderDetails.Any(y => y.SuspenseQuantity < y.Quantity));
                            break;
                    }
                }
                if (searchModel.OrderStatus.HasValue) {
                    query = query.Where(x => x.Status == searchModel.OrderStatus.Value);
                }
                if (searchModel.PrintStatus.HasValue) {
                    // 1: 已打印 2: 未打印
                    query = query.Where(x => x.IsPrinted == (searchModel.PrintStatus.Value == 1));
                }

                searchModel.Records = query.Count();
                pageIndex = (pageIndex - 1) * this._pageSize > searchModel.Records ? 1 : pageIndex;

                return query.OrderByDescending(x => x.CreatedDatetime).Skip((pageIndex - 1) * this._pageSize).Take(this._pageSize).ToList();
            }
        }

        public int GetTheMaxFlowNumber(string startString) {
            using (var ctx = this.Context) {
                ProcurementOrder procurementOrder = ctx.ProcurementOrders
                    .Where(x => x.Code.StartsWith(startString))
                    .OrderByDescending(x => x.Code)
                    .FirstOrDefault();
                int maxFlowNumber = procurementOrder == null
                    ? 0
                    : Convert.ToInt32(procurementOrder.Code.Substring(procurementOrder.Code.Length - 2, 2));
                return Math.Max(ctx.ProcurementOrders.Count(x => x.Code.StartsWith(startString)), maxFlowNumber);
            }
        }

        public void SaveGeneratedProdurementOrder(List<ProcurementOrder> procurementOrderList,
            List<ProduceConduct> produceConductList) {
            using (var ctx = this.Context) {
                using (DbContextTransaction trans = ctx.Database.BeginTransaction()) {
                    try {
                        procurementOrderList.ForEach(x => {
                            ctx.ProcurementOrders.Attach(x);
                            ctx.Entry(x).State = EntityState.Added;
                            x.ProcurementOrderDetails.ToList().ForEach(y => {
                                ctx.ProcurementOrderDetails.Attach(y);
                                ctx.Entry(y).State = EntityState.Added;
                            });
                        });
                        produceConductList.ForEach(x => {
                            ctx.ProduceConducts.Attach(x);
                            ctx.Entry(x).State = EntityState.Modified;
                        });

                        ctx.SaveChanges();
                        trans.Commit();
                    }
                    catch (Exception ex) {
                        trans.Rollback();
                        throw ex;
                    }
                }
            }
        }

        public List<MidProcurementOrder> GenerateProdurementOrder() {
            using (var ctx = this.Context) {
                var query = from a in ctx.ProduceConducts
                            join b in ctx.Products on a.ProductId equals b.ProductId
                            join c in ctx.BOMs.Where(x => x.BOMStatus == 2) on b.Code equals c.Code into cOutput
                            from c1 in cOutput.DefaultIfEmpty()
                            join d in ctx.BOMDetails on c1.BOMId equals d.BOMId into dOutput
                            from d1 in dOutput.DefaultIfEmpty()
                            join e in ctx.Quotations.Where(x => x.Status == 2) on d1.MaterialId equals e.MaterialId into eOutput
                            from e1 in eOutput.Take(1).DefaultIfEmpty()
                            where a.IsCompleted == false
                            select new MidProcurementOrder() {
                                ProduceConductId = a.ProduceConductId,
                                ProduceConductCode = a.Code,
                                VendorId = e1.VendorId,
                                MaterialId = d1.MaterialId,
                                SourceId = null,
                                ProcurementOrderId = a.ProcurementOrderId,
                                Quantity = a.BatchAmount * d1.SingleCosts,
                                Price = e1.IsMultiple
                                    ? (a.BatchAmount * d1.SingleCosts) > e1.PriceThresholdValue ? e1.PriceGreaterThan : e1.Price
                                    : e1.Price,
                                //Total = a.BatchAmount * d1.SingleCosts * e1.Price,
                                DeliveryDate = DbFunctions.AddDays(DateTime.Now, e1.PreProcurementCycle + e1.ProcurementCycle).Value
                            };

                List<MidProcurementOrder> midList = query.Distinct().OrderBy(x => x.VendorId).ToList();
                midList.ForEach(m => {
                    m.Total = m.Quantity * m.Price;
                    m.Vendors = ctx.Vendors
                        .Include("Basic")
                        .Include("Basic1")
                        .Include("Basic2")
                        .Include("User")
                        .Where(x => ctx.Quotations
                        .Where(y => m.MaterialId.HasValue && y.MaterialId == m.MaterialId)
                        .Any(z => z.VendorId == x.VendorId))
                        .ToList();
                });

                return midList;
            }
        }
    }
}
